
development environment
Journaux liées à cette note :
Journal du mardi 29 avril 2025 à 22:36
Depuis un an que j'effectue des missions Freelance, j'ai régulièrement besoin d'effectuer des changements dans des projets pour intégrer mes pratiques development kit, telles que l'utilisation de Mise, .envrc, docker-compose.yml, un README guidé, etc.
Généralement, ces missions Freelance sont courtes et je ne suis pas missionné pour faire des propositions d'amélioration de l'environnements de développement.
En un an, j'ai été confronté à cette problématique à cinq reprises.
Jusqu'à présent, j'ai utilisé la méthode suivante :
- J'ai intégré mon development kit dans une branche
sklein-devkit
- Cette branche m'a ensuite servi de base pour créer des branches destinées à traiter mes issues, nommées sous la forme
sklein-devkit-issue-xxx
- Et pour finir, je transfère mes commits avec
git cherry-pick
dans une branche du typeissue-xxx
que je soumettais dans une Merge Request ou Pull Request.
À la base, ce workflow de développement n'est pas très agréable à utiliser, et devient particulièrement complexe lorsque je dois effectuer des git pull --rebase
sur la branche sklein-devkit
!
Dans les semaines à venir, pour le projet Albert Conversation, je dois trouver une solution élégante pour gérer un cas similaire. Il s'agit de maintenir des modifications (série de patchs) du projet https://github.com/open-webui/open-webui qui :
- seront soit intégrées au projet upstream après plusieurs semaines ou mois
- soit resteront spécifiques au projet Albert Conversation et ne seront jamais intégrées en upstream, comme par exemple l'intégration du Système de Design de l'État.
Je me souviens avoir été marqué par l'histoire du projet Real-Time Linux mentionnée dans l'épisode 118 du podcast de Clever Cloud : les développeurs de Real-Time Linux ont maintenu pendant 20 ans toute une série de patchs avant de finir par être intégrés dans le kernel upstream (source : la conférence "PREEMPT_RT over the years") !
Voici la liste des patchs maintenus par l'équipe Real-Time Linux :
└── patches
├── 0001-arm-Disable-jump-label-on-PREEMPT_RT.patch
├── 0001-ARM-vfp-Provide-vfp_state_hold-for-VFP-locking.patch
├── 0001-drm-i915-Use-preempt_disable-enable_rt-where-recomme.patch
├── 0001-hrtimer-Use-__raise_softirq_irqoff-to-raise-the-soft.patch
├── 0001-powerpc-Add-preempt-lazy-support.patch
├── 0001-sched-Add-TIF_NEED_RESCHED_LAZY-infrastructure.patch
├── 0002-ARM-vfp-Use-vfp_state_hold-in-vfp_sync_hwstate.patch
├── 0002-drm-i915-Don-t-disable-interrupts-on-PREEMPT_RT-duri.patch
├── 0002-locking-rt-Remove-one-__cond_lock-in-RT-s-spin_trylo.patch
├── 0002-powerpc-Large-user-copy-aware-of-full-rt-lazy-preemp.patch
├── 0002-sched-Add-Lazy-preemption-model.patch
├── 0002-timers-Use-__raise_softirq_irqoff-to-raise-the-softi.patch
├── 0002-tracing-Record-task-flag-NEED_RESCHED_LAZY.patch
├── 0003-ARM-vfp-Use-vfp_state_hold-in-vfp_support_entry.patch
├── 0003-drm-i915-Don-t-check-for-atomic-context-on-PREEMPT_R.patch
├── 0003-locking-rt-Add-sparse-annotation-for-RCU.patch
├── 0003-riscv-add-PREEMPT_LAZY-support.patch
├── 0003-sched-Enable-PREEMPT_DYNAMIC-for-PREEMPT_RT.patch
├── 0003-softirq-Use-a-dedicated-thread-for-timer-wakeups-on-.patch
├── 0004-ARM-vfp-Move-sending-signals-outside-of-vfp_state_ho.patch
├── 0004-drm-i915-Disable-tracing-points-on-PREEMPT_RT.patch
├── 0004-locking-rt-Annotate-unlock-followed-by-lock-for-spar.patch
├── 0004-sched-x86-Enable-Lazy-preemption.patch
├── 0005-drm-i915-gt-Use-spin_lock_irq-instead-of-local_irq_d.patch
├── 0005-sched-Add-laziest-preempt-model.patch
├── 0006-drm-i915-Drop-the-irqs_disabled-check.patch
├── 0007-drm-i915-guc-Consider-also-RCU-depth-in-busy-loop.patch
├── 0008-Revert-drm-i915-Depend-on-PREEMPT_RT.patch
├── 0053-serial-8250-Switch-to-nbcon-console.patch
├── 0054-serial-8250-Revert-drop-lockdep-annotation-from-seri.patch
├── Add_localversion_for_-RT_release.patch
├── ARM__Allow_to_enable_RT.patch
├── arm-Disable-FAST_GUP-on-PREEMPT_RT-if-HIGHPTE-is-als.patch
├── ARM__enable_irq_in_translation_section_permission_fault_handlers.patch
├── netfilter-nft_counter-Use-u64_stats_t-for-statistic.patch
├── POWERPC__Allow_to_enable_RT.patch
├── powerpc_kvm__Disable_in-kernel_MPIC_emulation_for_PREEMPT_RT.patch
├── powerpc_pseries_iommu__Use_a_locallock_instead_local_irq_save.patch
├── powerpc-pseries-Select-the-generic-memory-allocator.patch
├── powerpc_stackprotector__work_around_stack-guard_init_from_atomic.patch
├── powerpc__traps__Use_PREEMPT_RT.patch
├── riscv-add-PREEMPT_AUTO-support.patch
├── sched-Fixup-the-IS_ENABLED-check-for-PREEMPT_LAZY.patch
├── series
├── sysfs__Add__sys_kernel_realtime_entry.patch
└── tracing-Remove-TRACE_FLAG_IRQS_NOSUPPORT.patch
46 files
J'ai été impressionné, je me suis demandé comment cette équipe a réuissi à gérer ce projet aussi complexe sur une si longue durée sans finir par se perdre !
Real-Time Linux n'est pas le seul projet qui propose des versions patchées du kernel, c'est le cas aussi du projet Xen, Openvz, etc.
J'ai essayé de comprendre le workflow de développement de ces projets. Avec l'aide de Claude.ia, il semble que ces projets utilisent un outil comme quilt qui permet de gérer des séries de patchs.
Il semble aussi que Debian utilise quilt pour gérer des patchs ajoutés aux packages :
Quilt has been incorporated into dpkg, Debian's package manager, and is one of the standard source formats supported from the Debian "squeeze" release onwards.
J'ai creusé un peu de sujet et à l'aide de Claude.ia j'ai découvert des alternatives "modernes" à quilt.
- Git lui-même :
git format-patch
pour créer des séries de patchesgit am
pour appliquer des patchesgit range-diff
pour comparer des séries de patches- Branches de fonctionnalités +
git rebase -i
pour organiser les commits
- Stacked Git (https://stacked-git.github.io/) :
- Topgit (https://github.com/mackyle/topgit) :
- Gère des changements de code sous forme de piles (stacks)
- Permet de maintenir des patches à long terme pour des forks
- Git Patchwork - (https://github.com/getpatchwork/patchwork) :
- Système de gestion et suivi des patches envoyés par email
- Utilisé par le noyau Linux et d'autres projets open source
- Guilt (http://repo.or.cz/w/guilt.git) :
- Jujutsu :
- Système de contrôle de version moderne basé sur Git
- Meilleure gestion des branches et séries de patches
- Git Series (https://github.com/git-series/git-series) :
- Outil pour travailler avec des séries de patches Git
- Permet de suivre l'évolution des séries au fil du temps
Après avoir jeté un œil sur chacun de ces projets, j'envisage de créer un playground pour tester Stacked Git.
Je précise que je n'ai pas eu l'occasion de tester l'installation sous Windows, hier j'ai essayé, mais je n'ai pas réussi à installer WSL2 sous Windows dans un Virtualbox lancé sous Fedora. Je suis à la recherche d'une personne pour tester si mes instructions d'installation sont valides ou non.
Merci à Alexandre 🤗 qui a pris le temps de tester l'installation sous WSL2 du playground que j'ai présenté dans "Playground qui présente comment je setup un projet Python Flask en 2025".
Le playground : https://github.com/stephane-klein/mise-python-flask-playground
Après quelques petites corrections https://github.com/stephane-klein/mise-python-flask-playground/commits/main/ Alexandre a réussi avec succès à installer et lancer tous les services sous Windows 11 avec WSL2.
C'est une très bonne nouvelle 🙂.
Cela ajoute une « corde à mon arc ». Jusqu'à présent, je précisais bien que mes development kit n'étaient pas compatible MS Windows. Je le mentionnais même dans mes annonces d'embauche, pour ne pas surprendre les candidats.
Maintenant, mes environnements de développement sont compatibles Linux, MacOS, et Linux 🙂.
Playground qui présente comment je setup un projet Python Flask en 2025
Je pense que cela doit faire depuis 2015 que je n'ai pas développé une application en Python Flask !
Entre 2008 et 2015, j'ai beaucoup itéré dans mes méthodes d'installation et de setup de mes environnements de développement Python.
D'après mes souvenirs, si je devais dresser la liste des différentes étapes, ça donnerai ceci :
- 2006 : aucune méthode, j'installe Python 🙂
- 2007 : je me bats avec setuptools et distutils (mais ça va, c'était plus mature que ce que je pouvais trouver dans le monde PHP qui n'avait pas encore imaginé composer)
- 2008 : je trouve la paie avec virtualenv
- 2010 : j'ai peur d'écrire des scripts en Bash alors à la place, j'écris un script
bootstrap.py
dans lequel j'essaie d'automatiser au maximum l'installation du projet - 2012 : je me bats avec buildout pour essayer d'automatiser des éléments d'installation. Avec le recul, je réalise que je n'ai jamais rien compris à buildout
- 2012 : j'utilise Vagrant pour fixer les éléments d'installation, je suis plutôt satisfait
- 2015 : je suis radicale, j'enferme tout l'environnement de dev Python dans un container de développement, je monte un path volume pour exposer le code source du projet dans le container. Je bricole en
entrypoint
avec la commande "sleep".
Des choses ont changé depuis 2015.
Mais, une chose que je n'ai pas changée, c'est que je continue à suivre le modèle The Twelve-Factors App et je continue à déployer tous mes projets packagé dans des images Docker. Généralement avec un simple docker-compose.yml
sur le serveur, ou alors Kubernetes pour des projets de plus grande envergure… mais cela ne m'arrive jamais en pratique, je travaille toujours sur des petits projets.
Choses qui ont changé : depuis fin 2018, j'ai décidé de ne plus utiliser Docker dans mes environnements de développement pour les projets codés en NodeJS, Golang, Python…
Au départ, cela a commencé par uniquement les projets en NodeJS pour des raisons de performance.
J'ai ensuite découvert Asdf et plus récemment Mise. À partir de cela, tout est devenu plus facilement pour moi.
Avec Asdf, je n'ai plus besoin "d'enfermer" mes projets dans des containers Docker pour fixer l'environnement de développement, les versions…
Cette introduction est un peu longue, je n'ai pas abordé le sujet principal de cette note 🙂.
Je viens de publier un playground d'un exemple de projet minimaliste Python Flask suivant mes pratiques de 2025.
Voici son repository : mise-python-flask-playground
Ce playground est "propulsé" par Docker et Mise.
J'ai documenté la méthode d'installation pour :
- Linux (Fedora (distribution que j'utilise au quotidien) et Ubuntu)
- MacOS avec Brew
- MS Windows avec WSL2
Je précise que je n'ai pas eu l'occasion de tester l'installation sous Windows, hier j'ai essayé, mais je n'ai pas réussi à installer WSL2 sous Windows dans un Virtualbox lancé sous Fedora. Je suis à la recherche d'une personne pour tester si mes instructions d'installation sont valides ou non.
Briques technologiques présentes dans le playground :
- La dernière version de Python installée par Mise, voir .mise.toml
- Une base de données PostgreSQL lancé par Docker
- J'utilise named volumes comme expliqué dans cette note : 2024-12-09_1550
- Flask-SQLAlchemy
- Flask-Migrate
- Une commande
flask initdb
avec Click pour reset la base de données - Utiliser d'un template Jinja2 pour qui affiche les
users
en base de données
Voici quelques petites subtilités.
Dans le fichier alembic.ini j'ai modifié le paramètre file_template
parce que j'aime que les fichiers de migration soient classés par ordre chronologique :
[alembic]
# template used to generate migration files
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(slug)s
20250205_124639_users.py
20250205_125437_add_user_lastname.py
Ici le port de PostgreSQL est généré dynamiquement par docker compose :
postgres:
image: postgres:17
...
ports:
- 5432 # <= ici
Avec cela, fini les conflits de port quand je lance plusieurs projets en même temps sur ma workstation.
L'URL vers le serveur PostgreSQL est générée dynamiquement par le script get_postgres_url.sh
qui est appelé par le fichier .envrc
. Tout cela se passe de manière transparente.
J'initialise ici les extensions PostgreSQL :
def init_db():
db.drop_all()
db.session.execute(db.text('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'))
db.session.execute(db.text('CREATE EXTENSION IF NOT EXISTS "unaccent"'))
db.session.commit()
db.create_all()
et ici dans la première migration :
def upgrade():
op.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";')
op.execute('CREATE EXTENSION IF NOT EXISTS "unaccent";')
op.create_table('users',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('firstname', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
Journal du mardi 04 février 2025 à 16:46
Je souhaite créer un playground d'un development kit pour Python + PostgreSQL (via Docker) + Flask + Flask-Migrate, basé sur Mise.
J'ai la contrainte suivante : le development kit doit fonctionner sous MS Windows !
Je me dis que c'est une bonne occasion pour moi de tester Windows Subsystem for Linux 🙂.
Problème : je ne possède pas d'instance MS Windows.
#JaiDécouvert que depuis 2015, Microsoft met à disposition des ISOs officiels de MS Windows :
- ISO pour Windows 10 : https://www.microsoft.com/fr-fr/software-download/windows10ISO
- ISO pour Windows 11 : https://www.microsoft.com/fr-fr/software-download/windows11
- Virtual machine MS Windows pour VirtualBox et d'autres : https://developer.microsoft.com/en-us/windows/downloads/virtual-machines/
J'ai testé dans ce playground le lancement d'une Virtual machine MS Windows avec Vagrant : https://github.com/stephane-klein/vagrant-windows-playground
.
Cela a bien fonctionné 🙂.
J'ai aussi découvert le repository windows-vagrant
qui semble permettre de construire différents types d'images MS Windows avec Packer. Je n'ai pas essayé d'en construire une.
Ma définition et objectif d'un "Workspace" dans un "environnement de développement" ?
Dans une note précédente, j'ai donné ma définition et les objectifs d'un "Development kit".
Dans cette note, je souhaite donner ma définition et les objectifs d'un workspace dans un "environnements de développement".
Un workspace est un dossier, qui contient des paramètres de configuration spécifiques — généralement sous la forme de variables d'environnements — qui permettent d'interagir sur une ou plusieurs instances de services, d'un environnement précis.
Généralement ce dossier contient des guides d'instructions pour réaliser des actions spécifiques sur le workspace et des scripts de type "helpers".
Exemple de workspaces :
staging/remote-workspace/
: un workspace utilisé pour effectuer des actions sur les services déployés en staging sur des serveurs distants ;staging/local-workspace/
: un workspace d'installer localement des services dans les mêmes conditions que sur l'environnement staging ;development/local-workspace/
: un dossier workspace, qui permet de travailler — contribuer — localement sur le ou les services.
Voir aussi :
Journal du mercredi 04 décembre 2024 à 14:56
Alexandre a eu un breaking change avec Mise : https://github.com/jdx/mise/issues/3338.
Suite à cela, j'ai découvert que Mise va prévilégier l'utilisation du backend aqua plutôt que Asdf :
we are actively moving tools in the registry away from asdf where possible to backends like aqua and ubi which don't require plugins.
J'ai découvert au passage que Mise supporte de plus en plus de backend, par exemple Ubi et vfox.
Je constate qu'il commence à y avoir une profusion de "tooling version management" : Asdf,Mise, aqua, Ubi, vfox !
Je pense bien qu'ils ont chacun leurs histoires, leurs forces, leurs faiblesses… mais j'ai peur que cela me complique mon affaire : comment arriver à un consensus de choix de l'un de ces outils dans une équipe 🫣 ! Chaque développeur aura de bons arguments pour utiliser l'un ou l'autre de ces outils.
Constatant plusieurs fois que le développeur de Mise a fait des breaking changes qui font perdre du temps aux équipes, mon ami et moi nous sommes posés la question si, au final, il ne serait pas judicieux de revenir à Asdf.
D'autre part, au départ, Mise était une simple alternative plus rapide à Asdf, mais avec le temps, Mise prend en charge de plus en plus de fonctionnalités, comme une alternative à direnv , un système d'exécution de tâches, ou mise watch.
Souvent, avec des petits défauts très pénibles, voir par exemple, ma note "Le support des variables d'environments de Mise est limité, je continue à utiliser direnv".
Alexandre s'est ensuite posé la question d'utiliser un jour le projet devenv, un outil qui va encore plus loin, basé sur le système de package Nix.
Le projet devenv me fait un peu peur au premier abord, il gère "tout" :
- Comme Asdf et Mise : l'installation des outils, packages et langages
- Support de scripts "helper"
- Intégration de Docker
- Support de process
- Support du SDK Android
Il fait énormément de choses et je crains que la barrière à l'entrée soit trop haute et fasse fuir beaucoup de développeurs 🤔.
Tout cela me fait un peu penser à Bazel (utilisé par Google), Pants (utilisé par Twitter), Buck (utilisé par Facebook) et Please.
Tous ces outils sont puissants, je les ai étudiés en 2018 sans arrivée à les adopter.
Pour le moment, mes development kit nécessitent les compétences suivantes :
- Comprendre les rudiments d'un terminal Bash ;
- Arriver à installer et à utiliser Mise et direnv ;
- Maitriser Docker ;
- Savoir lire et écrire des scripts Bash de niveau débutant.
Déjà, ces quatre prérequis posent quelques fois des difficultés d'adoption.
Journal du mercredi 04 décembre 2024 à 10:14
#JaiDécouvert l'outil de "version manager" nommé aqua, une alternative à Mise et Asdf codé en Golang.
Ce projet semble avoir débuté en août 2021.
J'ai fait quelques recherches au sujet d'aqua sur Hacker News, j'ai trouvé très peu d'occurrences. J'ai trouvé "Ask HN: Homebrew, Asdf, Nix, or Other?".
Je pense qu'aqua est bien moins populaire que Asdf et Mise.
Au 4 décembre 2024 :
Je remplace direnv par la fonctionnalité env._source proposée Mise
Depuis avril 2019, j'utilise direnv dans pratiquement tous mes projets de développement informatique.
Ce matin je me suis demandé si avec les nouvelles fonctionnalités de gestion des variables d'environnement de Mise, je pouvais simplifier mes development kit 🤔.
Comme vous pouvez le voir dans install-and-configure-direnv-with-mise-skeleton
, actuellement mes development kit contiennent deux étapes de modifications de .bash_profile
ou .zshrc
. Exemple avec Zsh :
- Une étape pour configurer Mise :
$ echo 'eval "$(~/.local/bin/mise activate zsh)"' >> ~/.zshrc
$ source ~/.zsrhrc
- Seconde étape pour configurer direnv :
$ echo -e "\neval \"\$(direnv hook zsh)\"" >> ~/.zshrc
$ source ~/.zsrhrc
Voici la version simplifiée de ce skeleton basé sur "mise env._source
", avec une étape de configuration en moins :
https://github.com/stephane-klein/install-and-configure-mise-skeleton
Voici le contenu du fichier .mise.toml
:
[env]
_.source = "./.envrc.sh"
et le contenu de .envrc.sh
:
export HELLO_WORLD=foo
J'ai fait le choix de nommer ce fichier .envrc.sh
plutôt que .envrc
afin d'éviter des problèmes de compatibilité pour les utilisateurs qui ont direnv installé.
J'ai vérifié que les variables d'environnements "parents" sont bien conservées en cas de changement de variable d'environnement par Mise dans un sous dossier.
#JeMeDemande si je vais rencontrer des régressions par rapport à direnv 🤔.
J'ai décidé d'utiliser la fonctionnalité "mise env._source
" pendant quelques semaines pour me faire une opinion.
Update : voir 2024-11-06_2109.
development kit skeleton d'installation de direnv
Voici un dépôt GitHub qui contient les instructions que j'indiquais dans mes development kit, avant 2024, pour installer et configurer direnv, sous MacOS.
https://github.com/stephane-klein/install-and-configure-direnv-with-asdf-skeleton
Dans ce skeleton, direnv est installé avec asdf, voir le fichier racine ./.tool-versions
Et voici un exemple de skeleton, que j'utilise depuis novembre 2023, basé sur Mise :
https://github.com/stephane-klein/install-and-configure-direnv-with-mise-skeleton
Dans ce second skeleton, direnv est installé avec Mise, voir le fichier racine ./.mise.toml
.
Pourquoi j'utilise direnv dans mes development kit ?
Dans cette note, je souhaite expliquer pourquoi j'utilise et j'aime utiliser direnv.
Je suis toujours en train d'essayer de créer des development kit me permettant de travailler dans un environnements de développement toujours plus agréable.
J'ai découvert direnv en 2017 et j'ai commencé à l'utiliser dans un projet en avril 2019.
direnv s'utilise dans un shell, il charge et décharge automatiquement les variables d'environnement spécifiées dans un fichier .envrc
lorsqu'on entre et quitte un répertoire.
Exemple :
Voici les use cases listées sur le site officiel de direnv :
- Load 12factor apps environment variables
- Create per-project isolated development environments
- Load secrets for deployment
Voici quelques exemples concrets de mon utilisation de direnv :
- chargement automatique des paramètres de configuration et secret pour :
source .secret
export SCW_DEFAULT_ORGANIZATION_ID="...." # Get it, in https://console.scaleway.com/organization/settings
export SCW_ACCESS_KEY="..."
# This variable environments are used by Grafana Grizzly
export GRAFANA_URL=http://grafana.$(terraform output --json | jq -r '.server3_public_ip | .value').nip.io
export GRAFANA_USER=admin
export GRAFANA_TOKEN=password
- dans un projet Javascript :
export POSTGRES_ADMIN_URL="postgres://postgres:password@localhost:5432/myapp"
export POSTGRES_URL="postgres://webapp:password@localhost:5432/myapp"
export SMTP_HOST="127.0.0.1"
export SMTP_PORT="1025"
export SECRET="secret"
export APP_HOSTNAME="localhost"
export MAIL_FROM="noreply@example.com"
Les fichiers .envrc
sont des scripts shell, il est possible comme dans l'exemple au-dessus, d'utiliser source .secret
pour charger un autre fichier.
Je versionne toujours le fichier .envrc
, tandis que j'ignore (.gitignore
) les fichiers .secret
contenant des informations sensibles qui ne doivent pas être publiés dans le repository git.
Après avoir installé un development kit, j'indique à l'utilisateur les instructions à suivre pour installer les éléments de base du projet, comme Mise, Docker…, et la mise en place des secrets.
Mon objectif est ensuite de permettre à l'utilisateur de l'environnement de développement d'interagir avec des « scripts helpers » sans nécessiter de configuration préalable, simplement en entrant ou sortant des dossiers. Les dossiers sont utilisés comme « workspace contexte ».
Voir aussi : development kit skeleton d'installation de direnv